#include "rand.h"

#include <ctime>

#include <random>
#include <mutex>
#include <iostream>

#include <sfmt.h>

#include "report.h"

namespace afcore {

int CRandom::seed_ = 0;

/// @brief  随机生成器
thread_local static std::unique_ptr<CRandomSFMT> sfmtgenerator;


/// @brief  获取随机生成器
static CRandomSFMT* GetSFMTGen() {
  CRandomSFMT* gen = sfmtgenerator.get();

  if (!gen) {
    gen = new CRandomSFMT(static_cast<int>(std::time(nullptr) + CRandom::GetNextSeed()), 1);
    sfmtgenerator.reset(gen);
  }

  return gen;
}

CSMFTEngine& CSMFTEngine::Instance() {
  static CSMFTEngine s_instance;
  return s_instance;
}

CRandom& CRandom::Instance() {
  static CRandom s_instance;
  return s_instance;
}

uint32_t CRandom::Rand32() {
  return GetSFMTGen()->BRandom();
}

int32_t CRandom::RandI32(int32_t min /*= std::numeric_limits<int32_t>::min()*/,
                         int32_t max /*= std::numeric_limits<int32_t>::max()*/) {
  DBG_ASSERT(max >= min);
  return static_cast<int32_t>(GetSFMTGen()->IRandom(min, max));
}

uint32_t CRandom::RandU32(uint32_t min /*= std::numeric_limits<uint32_t>::min()*/,
                          uint32_t max /*= std::numeric_limits<uint32_t>::max()*/) {
  DBG_ASSERT(max >= min);
  return GetSFMTGen()->URandom(min, max);
}

float CRandom::RandFloat(float min /*= std::numeric_limits<float>::min()*/,
                         float max /*= std::numeric_limits<float>::max()*/) {
  DBG_ASSERT(max >= min);
  return static_cast<float>(GetSFMTGen()->Random() * (max - min) + min);
}

double CRandom::RandDouble() {
  return GetSFMTGen()->Random();
}

uint32_t CRandom::RandMS(uint32_t min, uint32_t max) {
  DBG_ASSERT(max >= min);
  DBG_ASSERT((std::numeric_limits<uint32_t>::max() /
              RMilliseconds::period::den) >= max);
  return GetSFMTGen()->URandom(min * RMilliseconds::period::den,
                               max * RMilliseconds::period::den);
}

RMilliseconds CRandom::RandTime(const RMilliseconds &min,
                                const RMilliseconds &max) {
  long long diff = max.count() - min.count();
  DBG_ASSERT(diff >= 0);
  DBG_ASSERT(diff < 0xFFFFFFFF);
  return min + RMilliseconds(RandU32(0, static_cast<uint32_t>(diff)));
}

double CRandom::RandChance() { return RandDouble() * 100.0; }

uint32_t CRandom::RandU32Weighted(size_t count, double const *weights) {
  std::discrete_distribution<uint32_t> dd(weights, weights + count);
  return dd(CSMFTEngine::Instance());
}

} // !namespace afcore